import sys
import wx
import os

from threading import Thread
from time import sleep
from traceback import format_exc
from shutil import move

from LMG.Utility.constants import *
from LMG.GUI.dupfiledialog import DupFileDialog
from LMG.Utility.helpers import forceunicode, removeEmptyDir

################################################################

class TorrentFiles(object):
    """
    Keep track of the files associated with a torrent
    """
    def __init__(self, torrent):
        self.torrent = torrent
              
        self.filename = self.torrent.info['name']
                
        # This one is to store the download progress ; if it's not stored, the progress
        # of an inactive torrent would stay only in the display of the list, and so it would
        # be lost if the GUI wouldn't display the column "progress". In this case it couldn't
        # be saved in the torrent.lst file.
        self.progress = 0.0
        self.downsize = 0.0
        self.upsize = 0.0
        self.sizeDone = 0.0

        # For metadataless torrents
        self.size_metadataless = 0.0
        self.sizeDone_metadataless = 0.0

        # Array to store file priorities
        # Just using a placeholder of 1 (Normal) for now
        if self.isFile():
            numfiles = 1
        else:
            numfiles = len(self.torrent.info['files'])
        self.filepriorities = [1] * numfiles

        self.floattotalsize = float(self.getSize())
        self.realsize = self.floattotalsize

        # Progress of individual files within torrent
        #
        # -1 = No information on file progress (or don't download file)
        # 0-100 = File downloading
        # >100 = File complete and in place
        self.fileprogress = [-1] * numfiles
        
        if not hasattr(self, "dest"):
            self.dest = None
        elif not self.isFile():
            self.dest = os.path.join(os.path.dirname(self.dest), self.filename)
        if not hasattr(self, "activelabel"):
            self.activelabel = ""

    def getActiveLabel(self):
        if not utility.labelmanager.get(self.activelabel):
            self.activelabel = ""
        return self.activelabel
    
    def setupDest(self, dest, forceasklocation, caller):
        """
        Set the download destination
        """
        self.dest = dest
        
        # Try reading the config file
        if self.dest is None:
            # Strip off quotes
            self.dest = self.torrent.torrentconfig.Read("dest").strip("\"")

        # Treat an empty string for dest the same as
        # not having one defined
        if not self.dest:
            self.dest = None

        # For new torrents, get the destination where to save the torrent
        if self.dest is None or forceasklocation:
            self.getDestination(forceasklocation, caller)

        # Treat an empty string for dest the same as
        # not having one defined
        if not self.dest:
            self.dest = None

    def onOpenDest(self, event = None, index = 0):
        """
        Opens file destination
        """
        return self.onOpenFileDest(index, pathonly = True)

    def onOpenFileDest(self, event = None, index = 0, pathonly = False):
        """
        Opens the file
        """
        dest = self.getSingleFileDest(index, pathonly, checkexists = False)
        
        # Check to make sure that what we're trying to get exists
        if dest is None or not os.access(dest, os.R_OK):
            # Error : file not found
            if dest is None:
                desttext = "???"
            else:
                desttext = dest
            dialog = wx.MessageDialog(None, 
                                      desttext + '\n\n' + _('File not found'), 
                                      _('Error'), 
                                      wx.ICON_ERROR)
            dialog.ShowModal()
            dialog.Destroy()
            return False
            
        # A file is completed if it either is a single file flagged as completed,
        # or is a file within a multi-file torrent flagged as "Done"
        # (i.e.: file is in-place)
        if pathonly:
            # Don't need to check if the torrent is complete if we're only
            # opening the path
            completed = False
        elif self.isFile():
            completed = self.torrent.status.completed
        else:
            completed = (self.fileprogress[index] > 100)
            
        # Don't need to check if the torrent is complete if we're only
        # opening the path
        if not pathonly and not completed:
            #Display Warning file is not complete yet
            dialog = wx.MessageDialog(None, 
                                      self.torrent.getColumnText(COL_TITLE) + '\n\n'+ \
                                      _('File not completed yet. Are you sure you want to open it?'), 
                                      _('Warning'), 
                                      wx.YES_NO|wx.ICON_EXCLAMATION)

            result = dialog.ShowModal()
            dialog.Destroy()
            if result != wx.ID_YES:
                return False

        try:
            Thread(target = utility.open_new, args=[dest]).start()
        except:
            pass
            
        return True
        
    def changeProcDest(self, dest, rentorrent = False):
        """
        Change download destination
        """
        self.dest = dest
        self.torrent.torrentconfig.writeBasicInfo()

        if rentorrent:
            # Update torrent name
            self.torrent.changeTitle(os.path.split(dest)[1])
        
        details = utility.window.details
        if details.getTorrent() == self.torrent:
            try:
                details.updateGeneral()
                details.updateFiles()
            except wx.PyDeadObjectError:
                pass
            
    def move(self, dest = None, name = None, rentorrent = False):
        if dest is None:
            dest = utility.config.Read('defaultmovedir')
            if utility.config.Read('addlabeltomovedir', "boolean"):
                dest = os.path.join(dest, self.getActiveLabel())                
        if name is None:
            name = self.filename
        if not os.access(dest, os.F_OK):
            try:
                os.makedirs(dest)
            except:
                return False
       
        #Wait thread a little bit for returning resource
        ##################################################
        sleep(0.5)

        if self.isFile():
            self.moveSingleFile(dest, name)
        else:
            self.moveDir(dest, name)

        self.changeProcDest(os.path.join(dest, name), rentorrent)
        
        return True
                               
    def moveSingleFile(self, dest, name):
        if not self.isFile():
            self.moveDir(dest, name)
            return
        
        originalname = os.path.split(self.dest)[1] # The original name
        source = os.path.split(self.dest)[0] # The original dir
        size = int(self.torrent.info['length']) # The size
        filename = name # The new name
        dest = dest # The new dest
        self.moveFiles({filename: [originalname, size]}, source, dest)

    def moveDir(self, dest, name):
        if self.isFile():
            self.moveSingleFile(dest, name)
            return

        destname = self.getProcDest()
       
        if destname is None:
            return
        filearray = {}

        movename = os.path.join(dest, name)
        for f in self.torrent.info['files']:
            for item in f['path']:
                size = int(f['length'])
                filearray[item] = [item, size]

        self.moveFiles(filearray, destname, movename)

        removeEmptyDir(destname, True)
            
    def moveFiles(self, filearray, source, dest):
        if source == dest and len(filearray) != 1:
            return
        
        dummyname = os.path.join(os.path.split(self.dest)[0], 'dummy')
        try:
            file(dummyname, 'w').close()
        except:
            pass
       
        overwrite = "ask"

        for filename in filearray:
            oldloc = os.path.join(source, filearray[filename][0])
            newloc = os.path.join(dest, filename)
            size = filearray[filename][1]
            done = False
            firsttime = True
                
            while not done:
                try:
                    # File exists
                    if os.access(oldloc, os.R_OK):
                        copyfile = True
                        
                        # Something already exists where we're trying to copy:
                        if os.access(newloc, os.F_OK):
                            # Default to "No"
                            result = -1
                            
                            if overwrite == "ask":
                                single = len(filearray) > 1
                                dialog = DupFileDialog(self.torrent, filename, single)
                                result = dialog.ShowModal()
                                dialog.Destroy()
                                if result == 2:
                                    overwrite = "yes"
                                elif result == -2:
                                    overwrite = "no"
                                    
                            if overwrite == "yes" or result > 0:
                                os.remove(newloc)
                            elif overwrite == "no" or result < 0:
                                copyfile = False
                                
                        if copyfile:
                            try:
                                os.renames(oldloc, newloc)
                            except:
                                move(oldloc, newloc)
                    done = True
                except:
                    # There's a very special case for a file with a null size referenced in the torrent
                    # but not retrieved just because of this null size : It can't be renamed so we
                    # just skip it.
                    if size == 0:
                        done = True
                    else:
                        #retry >_<;
                        if firsttime:
                            firsttime = False
                            sleep(0.1)
                        else:
                            done = True
                            
                            dialog = wx.MessageDialog(None, _('Error moving files') + "\n" + format_exc(), _('Error'), wx.ICON_ERROR)
                            dialog.ShowModal()
                            dialog.Destroy()
                            
        try:
            os.remove(dummyname)
        except:
            pass
                               
    def removeFiles(self):
        """
        Remove the files associated with a torrent
        """
        destination = self.getProcDest()
        
        if destination is None:
            return
      
        # Remove File
        ##################################################        
        firsttime = True
        while 1:
            #Wait thread a little bit for returning resource
            ##################################################
            sleep(0.5)
            try:
                if self.isFile():
                    #remove file
                    if os.access(destination, os.F_OK):
                        os.remove(destination)
                else:                  
                    # Only delete files from this torrent
                    # (should be safer this way)
                    subdirs = 0
                    for x in self.torrent.info['files']:
                        filename = destination
                        subdirs = max(subdirs, len(x['path']) - 1)
                        for i in x['path']:
                            filename = os.path.join(filename, i)
                        if os.access(filename, os.F_OK) and os.path.isfile(filename):
                            os.remove(filename)
                    
                    removeEmptyDir(destination, (subdirs > 0))
                break
            except:
                #retry >_<;
                if firsttime:
                    firsttime = False
                    sleep(0.1)
                else:                                        
                    dialog = wx.MessageDialog(None, _('Error deleting file') + "\n" + format_exc(), _('Error'), wx.ICON_ERROR)
                    dialog.ShowModal()
                    dialog.Destroy()
                    break
                    
    def getDest(self):
        """
        Returns the download destination
        """
        return self.dest
        
    def getDestination(self, forceasklocation = False, caller = ""):
        """
        Specify where to save the torrent
        """
        # Set destination location that will be used in next set destination dialog

        # No default directory (or default directory can't be found)
        defaultfolder = utility.config.Read('defaultfolder')
        if not os.access(defaultfolder, os.F_OK):
            try:
                os.makedirs(defaultfolder)
            except:
                forceasklocation = True
                
        if ((not utility.config.Read('setdefaultfolder', "boolean") or forceasklocation)
            and (caller not in ["web", "command"])):
            success, dest = self.torrent.dialogs.setDestination()
            if not success:
                try:
                    os.remove(dest)
                except:
                    pass
            else:
                if self.isFile():
                    self.dest = dest
                else:
                    self.dest = os.path.join(dest, self.filename)                    
        else:
            self.dest = os.path.join(utility.config.Read('defaultfolder'), self.filename)
            
    def getProcDest(self, pathonly = False, checkexists = True):
        # Set it to self.dest (should be fine for files)
        dest = self.dest
        
        # In the case of a multifile torrent, see where we're saving
        if not self.isFile():
            ## see if we're saving to a subdirectory or not
            existing = 0
            if os.path.exists(dest):
                if not os.path.isdir(dest):
                    dest = None
                elif os.listdir(dest):  # if it's not empty
                    for x in self.torrent.info['files']:
                        if os.path.exists(os.path.join(dest, x['path'][0])):
                            existing = 1
                    if not existing:
                        dest = os.path.join(dest, self.filename)
        elif pathonly:
            # Strip out just the path for a regular torrent
            dest = os.path.dirname(self.dest)
                        
        if checkexists and dest is not None and not os.access(dest, os.F_OK):
            return None
                        
        return dest
    
    def getSingleFileDest(self, index = 0, pathonly = False, checkexists = True):
        """
        Used for getting the path for a file in a multi-file torrent
        """
        if self.isFile():
            return self.getProcDest(pathonly, checkexists)

        basedest = self.getProcDest(pathonly = True, checkexists = False)
        
        # This isn't a valid file
        if index >= len(self.torrent.info['files']):
            # Just return the base path of the file
            return basedest
        
        dest = basedest
        
        fileinfo = self.torrent.info['files'][index]
        for item in fileinfo['path']:
            dest = os.path.join(dest, item)
                        
        if pathonly:
            dest = os.path.dirname(dest)
                   
        if checkexists and dest and not os.access(dest, os.F_OK):
            # Just return the base path of the file
            return basedest
            
        return dest
        
    def isFile(self):
        return 'length' in self.torrent.info
        
    def getSize(self, realsize = False):
        """
        Get the total size of all files in the torrent

        If realsize is True, only return the total size
        of files that aren't set to "download never"
        """
        if not self.torrent.HasMetadata():
            file_length = self.size_metadataless
        elif self.isFile():   #1 file for this torrent
            file_length = self.torrent.info['length']
        else:   # Directory torrent
            file_length = 0
            count = 0
            for x in self.torrent.info['files']:
                # If returning the real size, don't include files
                # set to "download never"
                if not realsize or self.filepriorities[count] != -1:
                    file_length += x['length']
                count += 1
        
        return file_length        
        
    def updateRealSize(self):
        self.realsize = self.getSize(realsize = True)
        
        self.torrent.updateColumns([COL_SIZE])
    
    def setFilePriorities(self, priority_array = None):
        """
        Set the priorities for all of the files in a multi-file torrent
        """
        if priority_array is not None:
            self.filepriorities = priority_array
            self.torrent.torrentconfig.writeFilePriorities()
            self.updateRealSize()
            self.updateFileProgress()
        
        engine = self.torrent.connection.engine
        if len(self.filepriorities) > 1 and engine is not None and engine.dow is not None:
            if engine.dow.fileselector.picker is not None:
                engine.dow.fileselector.set_priorities(self.filepriorities)
            
    def getFilePrioritiesAsString(self):
        notdefault = False
        text = ""
        if len(self.filepriorities) > 1:
            for entry in self.filepriorities:
                if entry != 1:
                    notdefault = True
                text += ('%d,' % entry)
            # Remove the trailing ","
            text = text[:-1]

        return notdefault, text
        
    def updateProgress(self):
        """
        update the download progress
        """
        if not self.torrent.status.isActive():
            # Don't need to update if the torrent isn't active
            return

        engine = self.torrent.connection.engine
        self.downsize = engine.downsize['old'] + engine.downsize['new']
        self.upsize = engine.upsize['old'] + engine.upsize['new']
        if self.torrent.status.isActive(checking = False, pause = False):
            self.progress = engine.progress                    
            self.sizeDone = engine.sizeDone  
                
    def updateFileProgress(self, statistics = None):
        """
        Update the progress for files within a multi-file torrent
        """
        if self.isFile():
            details = utility.window.details
            if details.getTorrent() == self.torrent and utility.frame.GUIupdate:
                details.filesPage.fileList.onUpdateColumns([FILEINFO_PROGRESS])
            return
        
        # Clear progress for all files that are set to never download
        for i in range(len(self.filepriorities)):
            priority = self.filepriorities[i]
            if priority == -1:
                self.fileprogress[i] = -1
                
        if statistics is not None and statistics.filelistupdated.isSet():
            for i in range(len(statistics.filecomplete)):
                progress = None
                
                if self.filepriorities[i] == -1:
                    # Not download this file
                    pass
                elif statistics.fileinplace[i]:
                    # File is done
                    progress = 101
                elif statistics.filecomplete[i]:
                    # File is at complete, but not done
                    progress = 100
                else:
                    # File isn't complete yet
                    frac = statistics.fileamtdone[i]
                    if frac:
                        try:
                            progress = (frac * 100)
                        except:
                            pass

                if progress is None:
                    progress = -1
                    
                self.fileprogress[i] = int(progress)
                
            statistics.filelistupdated.clear()
        
        details = utility.window.details
        if details.getTorrent() == self.torrent and utility.frame.GUIupdate:
            details.filesPage.fileList.onUpdateColumns([FILEINFO_SIZE, FILEINFO_PROGRESS])

    def getSpaceAllocated(self):
        """
        See how much more space is allocated to this torrent
        """
        allocated = 0L
        if self.isFile():
            if os.path.exists(self.dest):
                allocated = os.path.getsize(self.dest)
        else:
            count = 0
            for f in self.torrent.info['files']:
                # Don't include space taken by disabled files
                if self.filepriorities[count] != -1:
                    filename = self.getProcDest()
                    
                    try:
                        for item in f['path']:
                            filename = os.path.join(filename, item)
                        if os.path.exists(filename):
                            allocated += os.path.getsize(filename)
                    except:
                        pass
                count += 1
                    
        return allocated
    
    def getSpaceNeeded(self, realsize = True):
        """
        See how much space is needed by this torrent
        """
        # Shouldn't need any more space if the file is complete
        if self.torrent.status.completed:
            return 0L
        
        # See how much space the torrent needs vs. how much is already allocated
        space = self.getSize(realsize = realsize) - self.getSpaceAllocated() 
        if space < 0:
            space = 0L
        return space

        
